"""
函数是用于组织和重用代码的一个重要工具。
函数可以将一段逻辑封装起来，通过调用函数来执行这些逻辑，从而提高代码的可读性和复用性。
def 函数名(若干参数):
    '''
    函数体
    '''
    return 返回值  # 可选
def关键字表示英文单词define，用来告诉Python你正在定义一个函数。
def关键字是定义函数的起始点。
函数名是你给函数取的名字，遵循Python的变量命名规则（例如，不以数字开头，不使用保留关键字等等）。
函数名应当具有描述性，以便于理解函数的作用。
参数（parameters）是在函数定义中括号内列出的变量名。
参数用于接收函数调用时传递给函数的值。函数可以有多个参数，也可以没有参数。
多个参数之间应该用英文逗号","间隔开。
return也是Python中保留的关键字。
return语句用于指定函数的返回值。
return后面可以跟任何表达式，函数会计算该表达式并返回其值。
如果没有return语句，或者return语句后没有表达式，函数默认返回None。
形式参数是在函数定义中使用的参数。
这些参数充当占位符，当函数被调用时，它们被实际参数替代。
形式参数定义了函数可以接受的输入。
实际参数是在函数调用时传递给函数的值。
这些值用于替换函数定义中的形式参数。
实际参数提供了函数在特定调用时所需的输入。
"""
def my_add(a, b):
    ans = a + b
    return ans
#位置传参指的是，实际参数按照位置依次传递给形式参数。
def greet(name, message):
    return f"{message}, {name}!"

print(greet("Alice", "Hello"))  # 输出：Hello, Alice!
#关键字传参指的是，实际参数可以通过参数名传递给形式参数。
def greet(name, message):
    return f"{message}, {name}!"

print(greet(message="Good morning", name="Bob"))  # 输出：Good morning, Bob!
print(greet(name="Bob", message="Good morning"))  # 输出：Good morning, Bob!
#函数定义的时候可以设置参数默认值。
# 如果形式参数在定义时有默认值，可以在调用时省略对应的实际参数。
def greet(message, name = "Alice"):
    return f"{message}, {name}!"

print(greet("Hello"))               # 输出：Hello, Alice!
print(greet("Hello", "Bob"))        # 输出：Hello, Bob!
#可变参数：使用*args和**kwargs，可以传递可变数量的位置参数和关键字参数。
def summarize(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)

summarize(1, 2, 3, name="Alice", age=30)
# 输出：
# args: (1, 2, 3)
# kwargs: {'name': 'Alice', 'age': 30}
"""
在详细介绍不可变数据类型和可变数据类型作为函数参数的区别之前，我们重新审视一下它们之间的区别。
- 不可变数据类型：创建后其值不能改变。引用和拷贝行为类似于新对象的创建。对于不可变数据类型而言，引用和拷贝没有太大区别。
- 可变数据类型：创建后其值可以改变。引用只是复制了对象的引用，修改引用会影响原对象。浅拷贝复制对象的引用，但不会创建子对象的副本。深拷贝会递归地复制对象及其所有子对象
常见的不可变数据类型有：
- 整数、浮点数、字符串、元组、布尔值
可变数据类型在创建后，其值可以被改变。
常见的可变数据类型有：
- 列表、字典、集合（后两者在后面会学到）
"""
# 在Python中，变量存储的是数据的引用，对变量的赋值操作只是复制了这个引用，而不是数据本身。
# 对于不可变数据类型和可变数据类型，引用的行为表现是一样的，但结果不同。
a = 10    # 对a进行赋值，将值为10的数字对象赋值给a
b = a     # 对b进行赋值，将值为10的数字对象赋值给b
b = 20    # 修改b，这里的修改是对b进行重新赋值，将值为20的对象赋值给b
print(a)  # 输出: 10
print(b)  # 输出: 20
#可变数据类型的引用与修改
# a 和 b 都引用同一个列表对象。修改 b 的内容也会影响 a，因为列表是可变的，b.append(4) 操作修改了列表对象的内容。
a = [1, 2, 3]    # 对a进行赋值，将值为[1, 2, 3]的列表对象赋值给a
b = a            # 对b进行赋值，将值为[1, 2, 3]的列表对象赋值给b
b.append(4)      # 修改b，对该值为[1, 2, 3]的列表对象进行修改，a也跟着修改，因为引用了同一个对象
print(a)         # 输出: [1, 2, 3, 4]
print(b)         # 输出: [1, 2, 3, 4]
# 可变数据类型的浅拷贝
# 如果我们希望将a的值[1, 2, 3]赋值给b的时候，b仅仅获得相同的值，而不是和a引用同一个对象
# ，我们可以使用列表的copy()方法或者切片来进行。
a = [1, 2, 3]    # 对a进行赋值，将值为[1, 2, 3]的列表对象赋值给a
b = a.copy()     # 对b进行赋值，将值为[1, 2, 3]的列表对象的一个拷贝赋值给b，创建了一个拷贝
c = a[:]         # 对c进行赋值，将值为[1, 2, 3]的列表对象的一个拷贝赋值给c，创建了一个拷贝
b.append(4)      # 修改b，因为此时a和b并不引用同一个对象，因此a不会跟着修改
c.append(5)      # 修改c，因为此时a和c并不引用同一个对象，因此a不会跟着修改
print(a)         # 输出: [1, 2, 3]
print(b)         # 输出: [1, 2, 3, 4]
print(c)         # 输出: [1, 2, 3, 5]
#可变数据类型的深拷贝
# 当可变数据类型的维度不止一维时（譬如二维列表），仅仅使用copy()方法或切片是无法完成对内层可变数据类型的拷贝的
# 。譬如以下例子
a = [[1, 2], [3, 4]]
b = a.copy()

b.append(6)
print(a)  # 输出: [[1, 2], [3, 4]]
print(b)  # 输出: [[1, 2], [3, 4], 6]

b[1].append(5)
print(a)  # 输出: [[1, 2], [3, 4, 5]]
print(b)  # 输出: [[1, 2], [3, 4, 5], 6]
"""
对b自身而言的修改，譬如b.append(6)并不会影响a，因为a和b是两个不同列表对象的引用。
但是当我们尝试对b中的子列表进行修改时，譬如b[1].append(5)，会发现a[1]仍然随着b[1]的修改发生了改变。
这是因为，copy()方法（以及切片）均属于浅拷贝（shallow copy）。
浅拷贝仅仅是拷贝了原对象的引用，创建了一个原对象的副本，但不会拷贝对象本身的深层副本。
当我们对浅拷贝（上述例子中的b）中的子对象进行修改的时候，原对象（上述例子中的a）中对应的子对象也会发生修改。
为了解决这个问题，这类高维的可变数据类型的拷贝需要用到深拷贝（deep copy）
深拷贝会创建一个新的对象，并递归地复制所有子对象。
深拷贝后，原对象和新对象之间完全独立，任何一个对象（以及子对象）的修改都不会影响另一个对象。
"""
from copy import deepcopy

a = [[1, 2], [3, 4]]
b = deepcopy(a)

b.append(6)
print(a)  # 输出: [[1, 2], [3, 4]]
print(b)  # 输出: [[1, 2], [3, 4], 6]

b[1].append(5)
print(a)  # 输出: [[1, 2], [3, 4]]
print(b)  # 输出: [[1, 2], [3, 4, 5], 6]

# 数据类型可变性对函数的影响
# - 对于不可变数据类型，函数内部的修改不会影响外部变量，因为任何修改都会创建一个新的对象。
# - 对于可变数据类型，函数内部的修改会影响外部变量，因为修改的是同一个对象的引用。
"""
匿名函数，顾名思义就是不需要具体定义函数名的函数。
先看一个无参数函数的例子。假设我们需要一个return 1的函数，如果使用普通的函数定义方式，其代码为：
# 使用def关键字进行函数定义
def get_one():
    return 1
# 输出1
print(get_one())
如果我们使用lambda匿名函数，我们则不需要使用def关键字而改用lambda关键字，其代码为：
# 使用lambda匿名函数进行函数定义
# 注意这里的get_one_lambda看似是一个变量名
# 但实际上是一个函数名
get_one_lambda = lambda: 1

print(get_one_lambda)
# 输出形如 <function <lambda> at 0x00000249F7AD7B00> 的结果
# 这一长串复杂的十六进制数是函数get_one_lambda的内存地址
 
print(get_one_lambda())
# 在函数名get_one_lambda后面加上括号，才能正确地输出1
"""
"""
1. lambda匿名函数本质上是一个函数，而不是一个变量，使用lambda匿名函数可以得到一个函数。
2. 定义lambda匿名函数的语法为：lambda [形参]: 返回值，形参的数量可以为0，即支持定义无参数匿名函数。
3. 使用lambda匿名函数的语法为：(lambda [形参]: 返回值) ([实参])，一般情况下，实参的数量应该和定义的形参数量一致。
*注：上述语句中的中括号[]表示非必须结构。
"""